#!/usr/bin/env python3
"""Generate V2 voiceover with natural speed - edge-tts zh-CN-XiaoxiaoNeural rate=+5%, atempo=1.05"""

import os, json, sys, asyncio, edge_tts, subprocess, re

BASE = r'E:\集群文件夹\factory_os\short_video_real_data_pipeline\phase4b_90s_formal_sample\v22_kimi_claude_loop\full_story_douyin_video'
VO_DIR = os.path.join(BASE, '02_voiceover', 'v2')
os.makedirs(VO_DIR, exist_ok=True)

VOICE = 'zh-CN-XiaoxiaoNeural'
RATE = '+5%'
PITCH = '+0Hz'
FFMPEG = r'D:\AI_WORKSPACE\tools\ffmpeg\ffmpeg.exe'

# Trimmed script for ~85-95s target
SEGMENTS = [
    {"id": 1, "text": "我让 Claude Code 自己做视频。录屏失败、AI 跑偏、从 69 爬到 92。这不是特效，这是真实工作流。"},
    {"id": 2, "text": "Windows 后台抓不到桌面画面。试了五种录屏方案，全是空文件。录屏路线，彻底失败。"},
    {"id": 3, "text": "更糟的是，Claude Code 跑去研究亚马逊选品。ChatGPT 看不下去：放弃录屏，让它自己画界面。"},
    {"id": 4, "text": "于是用 Python 画终端界面、工具调用卡片。四个 AI 代理并行跑，进度条在涨，代码在敲。每条命令都来自真实执行日志。"},
    {"id": 5, "text": "每轮渲染完 Kimi 审核打分。v1 69 分，改字体 v2 72，加动画 v3 跳到 81。修字幕 v4 85，砍时长 v6 89。V9 冲到 92。"},
    {"id": 6, "text": "但下一轮有个硬编码没改。画面四个代理，底部只显示三个。92 直接砸到 76。"},
    {"id": 7, "text": "修了六项回到 88，但回不到 92 了。一个硬编码，一夜回到解放前。最终决定：回滚 V9，冻结。"},
    {"id": 8, "text": "V9 上线审核门户，HTTP 200 通过。11 轮迭代，1 次崩溃，从 69 到 92。AI 自己审核自己，自己修复自己，自己部署自己。"},
    {"id": 9, "text": "录屏失败、跑偏、崩过、又爬回来。执行、审核、修复、回归、交付。这才是真正的 AI 工作流。"},
]

async def generate_segment(seg_id, text, vo_dir):
    """Generate a single TTS segment"""
    mp3_path = os.path.join(vo_dir, f'seg_{seg_id:02d}_v2.mp3')
    print(f"Generating segment {seg_id}...")
    communicate = edge_tts.Communicate(text, VOICE, rate=RATE, pitch=PITCH)
    await communicate.save(mp3_path)

    # Get duration
    result = subprocess.run([FFMPEG, '-i', mp3_path], capture_output=True, text=True)
    m = re.search(r'Duration: (\d+):(\d+):(\d+\.\d+)', result.stderr)
    dur = 0
    if m:
        h, mi, s = int(m.group(1)), int(m.group(2)), float(m.group(3))
        dur = h*3600 + mi*60 + s
    print(f"  Segment {seg_id}: {dur:.1f}s - {len(text)} chars")
    return {"id": seg_id, "text": text, "duration": dur, "mp3": mp3_path}

async def main():
    results = []
    for seg in SEGMENTS:
        result = await generate_segment(seg["id"], seg["text"], VO_DIR)
        results.append(result)

    # Build concat file with atempo filter applied
    atempo_val = '1.05'
    concat_file = os.path.join(VO_DIR, 'filter_concat_v2.txt')
    with open(concat_file, 'w', encoding='utf-8') as f:
        for r in results:
            f.write(f"file '{r['mp3']}'\n")

    full_wav = os.path.join(VO_DIR, 'voiceover_master_v2.wav')
    full_mp3 = os.path.join(VO_DIR, 'voiceover_master_v2.mp3')

    # First concat all segments then apply mild atempo
    temp_concat = os.path.join(VO_DIR, '_temp_concat.mp3')
    subprocess.run([
        FFMPEG, '-y', '-f', 'concat', '-safe', '0',
        '-i', concat_file,
        '-c', 'copy',
        temp_concat
    ], capture_output=True)

    # Apply mild atempo
    subprocess.run([
        FFMPEG, '-y',
        '-i', temp_concat,
        '-filter:a', f'atempo={atempo_val}',
        '-acodec', 'pcm_s16le', '-ar', '44100', '-ac', '1',
        full_wav
    ], capture_output=True)

    # MP3 version
    subprocess.run([
        FFMPEG, '-y', '-i', full_wav,
        '-codec:a', 'libmp3lame', '-q:a', '2',
        full_mp3
    ], capture_output=True)

    # Get total duration
    result = subprocess.run([FFMPEG, '-i', full_wav], capture_output=True, text=True)
    m = re.search(r'Duration: (\d+):(\d+):(\d+\.\d+)', result.stderr)
    total_dur = 0
    if m:
        h, mi, s = int(m.group(1)), int(m.group(2)), float(m.group(3))
        total_dur = h*3600 + mi*60 + s

    # Clean up temp
    if os.path.exists(temp_concat):
        os.remove(temp_concat)

    total_chars = sum(len(s["text"]) for s in SEGMENTS)
    segments_info = []
    for r in results:
        segments_info.append({
            "segment": r["id"],
            "file": os.path.basename(r["mp3"]),
            "duration_sec_orig": round(r["duration"], 1),
            "duration_sec_final": round(r["duration"] / float(atempo_val), 1),
            "text_preview": r["text"][:50]
        })

    manifest = {
        "voice": VOICE,
        "rate": RATE,
        "pitch": PITCH,
        "atempo": atempo_val,
        "total_segments": len(results),
        "total_duration_sec": round(total_dur, 1),
        "total_chars": total_chars,
        "chars_per_sec": round(total_chars / total_dur, 2) if total_dur > 0 else 0,
        "segments": segments_info
    }

    json_path = os.path.join(VO_DIR, 'voiceover_segments_v2.json')
    with open(json_path, 'w', encoding='utf-8') as f:
        json.dump(manifest, f, ensure_ascii=False, indent=2)

    print(f"\n{'='*50}")
    print(f"V2 VOICEOVER COMPLETE")
    print(f"{'='*50}")
    print(f"Total duration: {total_dur:.1f}s")
    print(f"Total chars: {total_chars} ({total_chars/total_dur:.2f} cps)")
    print(f"Atempo: {atempo_val}")
    print(f"Full WAV: {full_wav}")
    print(f"Full MP3: {full_mp3}")
    print(f"Manifest: {json_path}")

if __name__ == '__main__':
    asyncio.run(main())
